// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package types

import (
	"go/ast"
	"go/token"
	"strconv"
)

// ----------------------------------------------------------------------------------------
// API 

// 结构表示结构类型。
type Struct struct {
	fields []*Var   // 字段！=nil表示结构已设置（可能带有len（fields）==0）
	tags   []string // 字段标记；nil如果没有标记
}

// NewStruct返回一个包含给定字段和相应字段标记的新结构。
// 如果索引为i的字段有一个标记，则标记[i]必须是该标记，但len（标记）可能是
// 只要需要保留索引为i最大的标记即可。因此，
// 如果没有字段有标记，则标记可能为零。
func NewStruct(fields []*Var, tags []string) *Struct {
	var fset objset
	for _, f := range fields {
		if f.name != "_" && fset.insert(f) != nil {
			panic("multiple fields with the same name")
		}
	}
	if len(tags) > len(fields) {
		panic("more tags than fields")
	}
	s := &Struct{fields: fields, tags: tags}
	s.markComplete()
	return s
}

// NumFields返回结构中的字段数（包括空白字段和嵌入字段）。
func (s *Struct) NumFields() int { return len(s.fields) }

// 字段返回0<=i<NumFields（）的第i个字段。
func (s *Struct) Field(i int) *Var { return s.fields[i] }

// Tag返回0<=i<NumFields（）的第i个字段标记。
func (s *Struct) Tag(i int) string {
	if i < len(s.tags) {
		return s.tags[i]
	}
	return ""
}

func (t *Struct) Underlying() Type { return t }
func (t *Struct) String() string   { return TypeString(t, nil) }

// ---------------------------------------------------------------
// 实现

func (s *Struct) markComplete() {
	if s.fields == nil {
		s.fields = make([]*Var, 0)
	}
}

func (check *Checker) structType(styp *Struct, e *ast.StructType) {
	list := e.Fields
	if list == nil {
		styp.markComplete()
		return
	}

	// 结构字段和标记
	var fields []*Var
	var tags []string

	// 对于双重声明检查
	var fset objset

	// 当前字段类型和标记
	var typ Type
	var tag string
	add := func(ident *ast.Ident, embedded bool, pos token.Pos) {
		if tag != "" && tags == nil {
			tags = make([]string, len(fields))
		}
		if tags != nil {
			tags = append(tags, tag)
		}

		name := ident.Name
		fld := NewField(pos, check.pkg, name, typ, embedded)
		// 规范：“在结构中，非空字段名称必须是唯一的。”
		if name == "_" || check.declareInSet(&fset, pos, fld) {
			fields = append(fields, fld)
			check.recordDef(ident, fld)
		}
	}

	// addInvalid将无效类型的嵌入字段添加到
	// 有错误的字段的结构中；这将使结构字段的数量与源保持同步，只要这些字段是uu或具有不同的名称u#25627）。
	addInvalid := func(ident *ast.Ident, pos token.Pos) {
		typ = Typ[Invalid]
		tag = ""
		add(ident, true, pos)
	}

	for _, f := range list.List {
		typ = check.varType(f.Type)
		tag = check.tag(f.Tag)
		if len(f.Names) > 0 {
			// 命名字段
			for _, name := range f.Names {
				add(name, false, name.Pos())
			}
		} else {
			// 嵌入式字段
			// 规范：“嵌入式类型必须指定为类型名T或
			// 指向非接口类型名*T的指针，而T本身不能是
			// 指针类型。”
			pos := f.Type.Pos()
			name := embeddedFieldIdent(f.Type)
			if name == nil {
				check.invalidAST(f.Type, "embedded field type %s has no name", f.Type)
				name = ast.NewIdent("_")
				name.NamePos = pos
				addInvalid(name, pos)
				continue
			}
			add(name, true, pos)

			// 因为我们有一个名称，所以typ必须是T或*T的形式，其中T是一个（命名或别名）类型的名称
			// 而T（=deref（typ））必须是T的类型。
			// 我们必须将此检查推迟到最后，因为我们不想实例化
			// （通过（T）一个可能不完整的类型。

			// 用于下面的闭包
			embeddedTyp := typ
			embeddedPos := f.Type

			check.later(func() {
				t, isPtr := deref(embeddedTyp)
				switch u := under(t).(type) {
				case *Basic:
					if t == Typ[Invalid] {
						// 在
						return
					}
					// 之前报告了错误。指针被视为常规指针
					if u.kind == UnsafePointer {
						check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be unsafe.Pointer")
					}
				case *Pointer:
					check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer")
				case *Interface:
					if isTypeParam(t) {
						// 此处的错误代码与
						// 无效嵌入的其他错误代码不一致，因为该限制可能会在
						// 将来放宽，因此不保证有新的错误代码。
						check.error(embeddedPos, _MisplacedTypeParam, "embedded field type cannot be a (pointer to a) type parameter")
						break
					}
					if isPtr {
						check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer to an interface")
					}
				}
			}).describef(embeddedPos, "check embedded type %s", embeddedTyp)
		}
	}

	styp.fields = fields
	styp.tags = tags
	styp.markComplete()
}

func embeddedFieldIdent(e ast.Expr) *ast.Ident {
	switch e := e.(type) {
	case *ast.Ident:
		return e
	case *ast.StarExpr:
		// /*T有效，但**T不是
		if _, ok := e.X.(*ast.StarExpr); !ok {
			return embeddedFieldIdent(e.X)
		}
	case *ast.SelectorExpr:
		return e.Sel
	case *ast.IndexExpr:
		return embeddedFieldIdent(e.X)
	case *ast.IndexListExpr:
		return embeddedFieldIdent(e.X)
	}
	return nil // 无效的嵌入字段
}

func (check *Checker) declareInSet(oset *objset, pos token.Pos, obj Object) bool {
	if alt := oset.insert(obj); alt != nil {
		check.errorf(atPos(pos), _DuplicateDecl, "%s redeclared", obj.Name())
		check.reportAltDecl(alt)
		return false
	}
	return true
}

func (check *Checker) tag(t *ast.BasicLit) string {
	if t != nil {
		if t.Kind == token.STRING {
			if val, err := strconv.Unquote(t.Value); err == nil {
				return val
			}
		}
		check.invalidAST(t, "incorrect tag syntax: %q", t.Value)
	}
	return ""
}
